package com.cx.repository;

import com.cx.entity.RedisEntity;
import com.cx.service.RedisService;
import com.cx.service.RedisTemplateAdapter;
import com.cx.support.RedisBatch;
import com.cx.support.RedisBatchExecute;
import com.cx.utils.BeanHelper;
import com.cx.utils.ProtoStuffUtil;
import com.cx.wrapper.ObjWrapper;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.hash.BeanUtilsHashMapper;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import redis.clients.jedis.Pipeline;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.*;

/**
 * Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer
 * you a more sophisticated interface than the plain {@link EntityManager} .
 * 
 * @author Alan Shu
 * @param <T> the type of the entity to handle
 * @param <ID> the type of the entity's identifier
 */
@Transactional(readOnly=true, rollbackFor = Exception.class)
public class BaseJpaRedisRepositoryImpl<T extends RedisEntity<ID>, ID extends Serializable>
        extends SimpleJpaRepository<T, ID> {

    private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!";

    private final RedisTemplate<String, ?> redisTemplate;

    private final BeanUtilsHashMapper<T> beanUtilsHashMapper;

    private final EntityManager em;

    private final Class<T> clazz;

    public BaseJpaRedisRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.clazz = domainClass;
        this.em = entityManager;
        this.redisTemplate = RedisService.getRedisTemplate(false);
        beanUtilsHashMapper = new BeanUtilsHashMapper(domainClass);
    }

    @Transactional
	public void delete(ID id) {
		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		try {
			super.delete(id);

            String key = key(id);
            Boolean flag = redisTemplate.hasKey(key);
            if(BooleanUtils.isTrue(flag)) {
                redisTemplate.delete(key);
            }
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Transactional
	public void delete(T entity) {
		Assert.notNull(entity, "The entity must not be null!");

		try {
			super.delete(entity);

            String key = key(entity.getId());
            Boolean flag = redisTemplate.hasKey(key);
            if(BooleanUtils.isTrue(flag)) {
                redisTemplate.delete(key);
            }
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Transactional
	public void delete(Iterable<? extends T> entities) {
		Assert.notNull(entities, "The given Iterable of entities not be null!");

		Set<String> keys = new HashSet<>();
		StringBuffer ids = new StringBuffer();
		try {
			entities.iterator().forEachRemaining(entity -> {
				ID id = entity.getId();
				keys.add(key(id));
				ids.append(id);
			});

			super.delete(entities);

			if(keys.size() == 0){
				return;
			}

			redisTemplate.delete(keys);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Transactional
	public void deleteInBatch(Iterable<T> entities) {
		Assert.notNull(entities, "The given Iterable of entities not be null!");

		Set<String> keys = new HashSet<>();
		StringBuffer ids = new StringBuffer();
		try {
			entities.iterator().forEachRemaining(entity -> {
				ID id = entity.getId();
				keys.add(key(id));
				ids.append(id);
			});

			super.deleteInBatch(entities);

			if(keys.size() == 0){
				return;
			}
			redisTemplate.delete(keys);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Transactional
	public void deleteAll() {
		try {
			super.deleteAll();

			Set<String> keys = entityKeys();
			if(keys.size() == 0){
				return;
			}
			redisTemplate.delete(keys);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Transactional
	public void deleteAllInBatch() {
		try {
			super.deleteAllInBatch();

			Set<String> keys = entityKeys();
			if(keys.size() == 0){
				return;
			}
			redisTemplate.delete(keys);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public T findOne(ID id) {
		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		try {
			T t = getOnlyOne(key(id));
			if(null != t) {
				return t;
			}

			t = super.findOne(id);

            if(null == t) {
                return t;
            }

			BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(key(t.getId()));
			BeanHelper.registerConvertUtils();
			Map<String, String> map = beanUtilsHashMapper.toHash(t);
            operations.putAll(map);

			return t;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public T getOne(ID id) {
		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

		try {
			T t = getOnlyOne(key(id));
			if(null != t) {
				return t;
			}

			t = super.getOne(id);

            if(null == t) {
                return t;
            }

			BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(key(t.getId()));
			BeanHelper.registerConvertUtils();
			Map<String, String> map = beanUtilsHashMapper.toHash(t);
            operations.putAll(map);

			return t;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public boolean exists(ID id) {
		Assert.notNull(id, ID_MUST_NOT_BE_NULL);

        try {
			Boolean exists = redisTemplate.hasKey(key(id));
			return (BooleanUtils.toBoolean(exists)) ? exists : super.exists(id);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public List<T> findAll() {
		String idskey = BeanHelper.key(keyspace(), "findAll", null, null);
		try {
            List<String> entitykeys = entityKeys(idskey);
		    if(!CollectionUtils.isEmpty(entitykeys)) {
                List<T> finalEntities = getHashDataOfPipelined(entitykeys);

                if (!CollectionUtils.isEmpty(finalEntities) && !CollectionUtils.isEmpty(entitykeys)) {
                    return finalEntities;
                }
            }

			final List<T> ts = super.findAll();

			if(CollectionUtils.isEmpty(ts)){
				return ts;
			}

            execMultiHashPipelined(ts, idskey);

			return ts;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public List<T> findAll(Iterable<ID> ids) {
    	if(ObjectUtils.isEmpty(ids) || !ids.iterator().hasNext()){
			return Lists.newArrayListWithCapacity(0);
		}

		if(ids.iterator().hasNext()){
			ids.iterator().forEachRemaining(id -> {
				if(null == id){
					throw new RuntimeException("The param ids can not be null!");
				}
			});
		}

        String idskey = BeanHelper.key(keyspace(), "findAll", new String[]{"ids"}, new Object[]{ids});
        List<String> idList = Lists.newArrayListWithCapacity(10);
        try {
            List<String> entitykeys = entityKeys(idskey);
            List<T> finalEntities = getHashDataOfPipelined(ids, idList);

            if(!CollectionUtils.isEmpty(finalEntities) && !CollectionUtils.isEmpty(entitykeys)){
                return finalEntities;
            }

            final List<T> ts = super.findAll(ids);

            if(CollectionUtils.isEmpty(ts)){
                return ts;
            }

            execMultiHashPipelined(ts, idskey, idList);

            return ts;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

	public List<T> findAll(Sort sort) {
        return findAllByCache(sort, null);
	}

	public Page<T> findAll(Pageable pageable) {
        return findAllPageByCache(pageable, null);
	}

	public T findOne(Specification<T> spec) {
        int conditionsHashcode = new String(ProtoStuffUtil.serialize(spec)).hashCode();
        String idskey = BeanHelper.key(keyspace(), "findOne", new String[]{"spec"}, new Object[]{conditionsHashcode});
        try {
            String entitykey = entityKey(idskey);
            if(StringUtils.isNotBlank(entitykey)) {
                T entity = getOnlyOne(entitykey);

                if (!ObjectUtils.isEmpty(entity)) {
                    return entity;
                }
            }

            final T t = super.findOne(spec);

            if(null == t) {
                return t;
            }

            BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(key(t.getId()));
            BeanHelper.registerConvertUtils();
            Map<String, String> map = beanUtilsHashMapper.toHash(t);
            operations.putAll(map);

            RedisService.delete(idskey);
            RedisService.putObjCache(idskey, t.getId());

            return t;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	public List<T> findAll(Specification<T> spec) {
        return findAllByCache(null, spec);
	}

	public Page<T> findAll(Specification<T> spec, Pageable pageable) {
        return findAllPageByCache(pageable, spec);
	}

	public List<T> findAll(Specification<T> spec, Sort sort) {
        return findAllByCache(sort, spec);
	}

	@Override
	public <S extends T> S findOne(Example<S> example) {
        int conditionsHashcode = new String(ProtoStuffUtil.serialize(example)).hashCode();
        String idskey = BeanHelper.key(keyspace(), "findOne", new String[]{"spec"}, new Object[]{conditionsHashcode});
        try {
            String entitykey = entityKey(idskey);
            if(StringUtils.isNotBlank(entitykey)) {
                S entity = (S)getOnlyOne(entitykey);//父类就是子类，故强转

                if (!ObjectUtils.isEmpty(entity)) {
                    return entity;
                }
            }

            final S s = super.findOne(example);

            if(null == s) {
                return s;
            }

            BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(key(s.getId()));
            BeanHelper.registerConvertUtils();
            Map<String, String> map = beanUtilsHashMapper.toHash(s);
            operations.putAll(map);

            RedisService.delete(idskey);
            RedisService.putObjCache(idskey, s.getId());

            return s;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	@Override
	public <S extends T> long count(Example<S> example) {
        int conditionsHashcode = new String(ProtoStuffUtil.serialize(example)).hashCode();
        String idskey = BeanHelper.key(keyspace(), "count", new String[]{"count"}, new Object[]{conditionsHashcode});

        try {
            ObjWrapper<Long> objWrapper = (ObjWrapper<Long>)countKey(idskey);

            if(!ObjectUtils.isEmpty(objWrapper)) {
                return objWrapper.getData();
            }

            final long l = super.count(example);

            RedisService.delete(idskey);
            RedisService.putObjCache(idskey, new ObjWrapper<Long>(l));

            return l;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	@Override
	public <S extends T> boolean exists(Example<S> example) {
        int conditionsHashcode = new String(ProtoStuffUtil.serialize(example)).hashCode();
        String idskey = BeanHelper.key(keyspace(), "exists", new String[]{"exists"}, new Object[]{conditionsHashcode});

        try {
            ObjWrapper<Boolean> objWrapper = (ObjWrapper<Boolean>)countKey(idskey);
            if(!ObjectUtils.isEmpty(objWrapper)) {
                return objWrapper.getData();
            }

            final boolean l = super.exists(example);

            RedisService.delete(idskey);
            RedisService.putObjCache(idskey, new ObjWrapper<Boolean>(l));

            return l;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	@Override
	public <S extends T> List<S> findAll(Example<S> example) {
        return findAllByCache(example, null);
	}

	@Override
	public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
        return findAllByCache(example, sort);
	}

	@Override
	public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
        return findAllPageByCache(example, pageable);
	}

	public long count() {
        int size = -1;
        int eks = entityKeys().size();
        if(eks > -1){
            size = eks;
        }
		return size == -1 ? super.count() : size;
	}

	public long count(Specification<T> spec) {
        int conditionsHashcode = new String(ProtoStuffUtil.serialize(spec)).hashCode();
        String idskey = BeanHelper.key(keyspace(), "count", new String[]{"count"}, new Object[]{conditionsHashcode});
        try {
            ObjWrapper<Long> objWrapper = (ObjWrapper<Long>)countKey(idskey);
            if(!ObjectUtils.isEmpty(objWrapper)) {
                return objWrapper.getData();
            }

            final long l = super.count(spec);

            RedisService.delete(idskey);
            RedisService.putObjCache(idskey, new ObjWrapper<Long>(l));

            return l;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	@Transactional
	public <S extends T> S save(S entity) {
		try{
            String key = key(entity.getId());
            Boolean flag = redisTemplate.hasKey(key);
            if(BooleanUtils.isTrue(flag)) {
                redisTemplate.delete(key);
            }

            super.save(entity);

			BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(key(entity.getId()));
			BeanHelper.registerConvertUtils();
			Map<String, String> map = beanUtilsHashMapper.toHash(entity);
            operations.putAll(map);

			redisTemplate.delete(redisTemplate.keys(keyspace() + ":finds:*"));
			return entity;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Transactional
	public <S extends T> S saveAndFlush(S entity) {
        try{
            String key = key(entity.getId());
            Boolean flag = redisTemplate.hasKey(key);
            if(BooleanUtils.isTrue(flag)) {
                redisTemplate.delete(key);
            }

            super.saveAndFlush(entity);

            BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(key(entity.getId()));
            BeanHelper.registerConvertUtils();
            Map<String, String> map = beanUtilsHashMapper.toHash(entity);
            operations.putAll(map);

            redisTemplate.delete(redisTemplate.keys(keyspace() + ":finds:*"));
            return entity;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	@Transactional
	public <S extends T> List<S> save(Iterable<S> entities) {
        try{
            super.save(entities);

//            RedisService.batchExecute(new RedisBatch() {
//                @Override
//                public void call(Pipeline pipeline) {
//                    entities.forEach(t -> { putAllToCache(pipeline, t); });
//			          pipeline.delete(keyspace() + ":finds:*");
//                }
//            });
            RedisService.execute(new RedisBatchExecute() {
                @Override
                public void call(RedisOperations operations) {
                    entities.forEach(t -> { putAllToCache(operations, t); });
					operations.delete(keyspace() + ":finds:*");
                }
            }, entities.hashCode()+"");

            return (List<S>)entities;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
	}

	@Transactional
	public void flush() {
        super.flush();
	}


	private String keyspace(){
		return RedisTemplateAdapter.cacheLevel2Pre() + getDomainClass().getSimpleName();
	}

	private String key(ID id){
		return keyspace() + ":ids:" + id;
	}

	private String key(String id){
		return keyspace() + ":ids:" + id;
	}

	private Set<String> entityKeys() {
		return redisTemplate.keys(keyspace() + ":ids:*");
	}

    private ObjWrapper countKey(String key){
        Boolean hasKey = redisTemplate.hasKey(key);
        if(BooleanUtils.isFalse(hasKey)){
            return null;
        }

        return RedisService.getObjCache(key, ObjWrapper.class);
    }

    private String entityKey(String key){
        Boolean hasKey = redisTemplate.hasKey(key);
        if(BooleanUtils.isFalse(hasKey)){
            return null;
        }

        return RedisService.getObjCache(key, String.class);
    }

	private List<String> entityKeys(String key){
		Boolean hasKey = redisTemplate.hasKey(key);
		if(BooleanUtils.isFalse(hasKey)){
			return null;
		}

		return RedisService.getListCache(key, String.class);
	}

    private T getOnlyOne(String key){
        Boolean hasKey = redisTemplate.hasKey(key);
        if(BooleanUtils.isFalse(hasKey)){
            return null;
        }
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(key);
        Map<String, String> entries = operations.entries();
		BeanHelper.registerConvertUtils();
        return beanUtilsHashMapper.fromHash(entries);
    }

    private List<T> getEntities(List<Object> result){
        List<T> list = Lists.newArrayListWithCapacity(result.size());
        result.stream().filter(p->null!=p).forEach(obj -> {
			Map<String, String> map = (Map<String, String>)obj;
            BeanHelper.registerConvertUtils();
			T entity = beanUtilsHashMapper.fromHash(map);
			if (Objects.nonNull(entity) && Objects.nonNull(entity.getId())) {
				list.add(entity);
			}
        });

        return list;
    }

    private <S extends T> List<S> getExtEntities(List<Object> result){
        List<S> list = Lists.newArrayListWithCapacity(result.size());
        BeanUtilsHashMapper<S> beanUtilsHashMapper = new BeanUtilsHashMapper(clazz);
        result.stream().filter(p->null!=p).forEach(obj -> {
			Map<String, String> map = (Map<String, String>)obj;
            BeanHelper.registerConvertUtils();
            S entity = beanUtilsHashMapper.fromHash(map);
            if (Objects.nonNull(entity)) {
                list.add(entity);
            }
        });

        return list;
    }

	private List<T> findAllByCache(Sort sort, Specification<T> spec) {
		String[] paramnames = new String[2];
		Object[] paramvals = new Object[2];
        if(!ObjectUtils.isEmpty(spec)){
            paramnames[0] = "spec";
            paramvals[0] = new String(ProtoStuffUtil.serialize(spec)).hashCode();
        }
        if(!ObjectUtils.isEmpty(sort)){
            paramnames[1] = "sort";
            paramvals[1] = new String(ProtoStuffUtil.serialize(sort)).hashCode();
        }

		String idskey = BeanHelper.key(keyspace(), "findAll", paramnames, paramvals);
        try {
            List<String> entitykeys = entityKeys(idskey);
            if(!CollectionUtils.isEmpty(entitykeys)) {
                List<T> finalEntities = getSpecHashDataOfPipelined(entitykeys);

				if (!CollectionUtils.isEmpty(finalEntities) && !CollectionUtils.isEmpty(entitykeys)) {
					return finalEntities;
				}
			}

            List<T> result = null;
            if(!ObjectUtils.isEmpty(spec) && ObjectUtils.isEmpty(sort)){
                result = super.findAll(spec, (Sort) null);
            }
            if(!ObjectUtils.isEmpty(sort) && ObjectUtils.isEmpty(spec)){
                result = super.findAll((Specification<T>) null, sort);
            }
            if(!ObjectUtils.isEmpty(sort) && !ObjectUtils.isEmpty(spec)){
                result = super.findAll(spec, sort);
            }
			if(CollectionUtils.isEmpty(result)){
				return result;
			}

            execMultiHashPipelined(result, idskey);

			return result;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

    private <S extends T> List<S> findAllByCache(Example<S> example, Sort sort) {
        String[] paramnames = new String[2];
        Object[] paramvals = new Object[2];
        if(!ObjectUtils.isEmpty(example)){
            paramnames[0] = "example";
            paramvals[0] = new String(ProtoStuffUtil.serialize(example)).hashCode();
        }
        if(!ObjectUtils.isEmpty(sort)){
            paramnames[1] = "sort";
            paramvals[1] = new String(ProtoStuffUtil.serialize(sort)).hashCode();
        }

        String idskey = BeanHelper.key(keyspace(), "findAll", paramnames, paramvals);
        try {
            List<String> entitykeys = entityKeys(idskey);
            if(!CollectionUtils.isEmpty(entitykeys)) {
                List<S> finalEntities = getSpecExtHashDataOfPipelined(entitykeys);

                if (!CollectionUtils.isEmpty(finalEntities) && !CollectionUtils.isEmpty(entitykeys)) {
                    return finalEntities;
                }
            }

            List<S> result = null;
            if(!ObjectUtils.isEmpty(example) && ObjectUtils.isEmpty(sort)){
                result = super.findAll(example, (Sort) null);
            }
            if(!ObjectUtils.isEmpty(example) && !ObjectUtils.isEmpty(sort)){
                result = super.findAll(example, sort);
            }
            if(CollectionUtils.isEmpty(result)){
                return result;
            }

            execMultiHashPipelined(idskey, result);

            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Page<T> findAllPageByCache(Pageable pageable, Specification<T> spec) {
        String[] paramnames = new String[2];
        Object[] paramvals = new Object[2];
        if(!ObjectUtils.isEmpty(spec)){
            paramnames[0] = "spec";
            paramvals[0] = new String(ProtoStuffUtil.serialize(spec)).hashCode();
        }
        if(!ObjectUtils.isEmpty(pageable)){
            paramnames[1] = "pageable";
            paramvals[1] = new String(ProtoStuffUtil.serialize(pageable)).hashCode();
        }

        String idskey = BeanHelper.key(keyspace(), "findAll", paramnames, paramvals);
        try {
            List<String> entitykeys = entityKeys(idskey);
            if(!CollectionUtils.isEmpty(entitykeys)) {
                List<T> finalEntities = getSpecHashDataOfPipelined(entitykeys);

                if (!CollectionUtils.isEmpty(finalEntities) && !CollectionUtils.isEmpty(entitykeys)) {
                    return new PageImpl(finalEntities);
                }
            }

            Page<T> result = null;
            if(!ObjectUtils.isEmpty(pageable) && ObjectUtils.isEmpty(spec)){
                result = super.findAll((Specification<T>) null, pageable);
            }
            if(!ObjectUtils.isEmpty(pageable) && !ObjectUtils.isEmpty(spec)){
                result = super.findAll(spec, pageable);
            }
            if(CollectionUtils.isEmpty(result.getContent())){
                return result;
            }

            execMultiHashPipelined(result.getContent(), idskey);

            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private <S extends T> Page<S> findAllPageByCache(Example<S> example, Pageable pageable) {
        String[] paramnames = new String[2];
        Object[] paramvals = new Object[2];
        if(!ObjectUtils.isEmpty(example)){
            paramnames[0] = "example";
            paramvals[0] = new String(ProtoStuffUtil.serialize(example)).hashCode();
        }
        if(!ObjectUtils.isEmpty(pageable)){
            paramnames[1] = "pageable";
            paramvals[1] = new String(ProtoStuffUtil.serialize(pageable)).hashCode();
        }

        String idskey = BeanHelper.key(keyspace(), "findAll", paramnames, paramvals);
        try {
            List<String> entitykeys = entityKeys(idskey);
            if(!CollectionUtils.isEmpty(entitykeys)) {
                List<S> finalEntities = getSpecExtHashDataOfPipelined(entitykeys);

                if (!CollectionUtils.isEmpty(finalEntities) && !CollectionUtils.isEmpty(entitykeys)) {
                    return new PageImpl(finalEntities);
                }
            }

            Page<S> result = super.findAll(example, pageable);
            if(CollectionUtils.isEmpty(result.getContent())){
                return result;
            }

            execMultiHashPipelined(idskey, result.getContent());

            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private List<T> getSpecHashDataOfPipelined(List<String> entitykeys){
        List<Object> result = RedisService.batchExecute(new RedisBatch() {
            @Override
            public void call(Pipeline pipeline) {
                entitykeys.forEach(key -> {
                    pipeline.hgetAll(key);
                });
            }
        });
        return getEntities(result);

//        List<Object> result = RedisService.executePipelined(new RedisBatchExecute() {
//            @Override
//            public void call(RedisOperations operations) {
//                entitykeys.forEach(key -> {
//                    BeanHelper.getOnlyOne(key, operations);
//                });
//            }
//        });
//        return getEntities(result);
    }

    private <S extends T> List<S> getSpecExtHashDataOfPipelined(List<String> entitykeys){
        List<Object> result = RedisService.batchExecute(new RedisBatch() {
            @Override
            public void call(Pipeline pipeline) {
                entitykeys.forEach(key -> {
                    pipeline.hgetAll(key);
                });
            }
        });
        return getExtEntities(result);

//		List<Object> result = RedisService.executePipelined(new RedisBatchExecute() {
//            @Override
//            public void call(RedisOperations operations) {
//                entitykeys.forEach(key -> {
//                    BeanHelper.getOnlyOne(key, operations);
//                });
//            }
//        });
//		return getExtEntities(result);
    }

    private List<T> getHashDataOfPipelined(List<String> entitykeys){
        List<Object> result = RedisService.batchExecute(new RedisBatch() {
            @Override
            public void call(Pipeline pipeline) {
                entitykeys.forEach(id -> {
                    pipeline.hgetAll(key(id));
                });
            }
        });
        return getEntities(result);

//		List<Object> result = RedisService.executePipelined(new RedisBatchExecute() {
//            @Override
//            public void call(RedisOperations operations) {
//                entitykeys.forEach(id -> {
//                    String key = key(id);
//                    BeanHelper.getOnlyOne(key, operations);
//                });
//            }
//        });
//		return getEntities(result);
    }

    private List<T> getHashDataOfPipelined(Iterable<ID> ids, List<String> idList){
        List<Object> result = RedisService.batchExecute(new RedisBatch() {
            @Override
            public void call(Pipeline pipeline) {
                ids.forEach(id -> {
                	if(null != id) {
						idList.add(id + "");
						pipeline.hgetAll(key(id));
					}
                });
            }
        });
        return getEntities(result);

//		List<Object> result = RedisService.executePipelined(new RedisBatchExecute() {
//            @Override
//            public void call(RedisOperations operations) {
//                ids.forEach(id -> {
//                    idList.add(id+"");
//                    String key = key(id);
//                    BeanHelper.getOnlyOne(key, operations);
//                });
//            }
//        });
//		return getEntities(result);
    }

    private void execMultiHashPipelined(List<T> ts, String idskey){
//        RedisService.batchExecute(new RedisBatch() {
//            @Override
//            public void call(Pipeline pipeline) {
//                List<String> ids = Lists.newArrayListWithCapacity(10);
//                ts.forEach(t -> {
//                    ids.add(t.getId()+"");
//                    putAllToCache(pipeline, t);
//                });
//
//                RedisService.delete(idskey);
//                RedisService.putListCache(idskey, ids);
//            }
//        });
        RedisService.execute(new RedisBatchExecute() {
            @Override
            public void call(RedisOperations operations) {
                List<String> ids = Lists.newArrayListWithCapacity(10);
                ts.forEach(t -> {
                    ids.add(t.getId()+"");
                    putAllToCache(operations, t);
                });

                RedisService.delete(idskey);
                RedisService.putListCache(idskey, ids);
            }
        }, ts.hashCode()+"");
    }

    private <S extends T> void execMultiHashPipelined(String idskey, List<S> ts){
//        RedisService.batchExecute(new RedisBatch() {
//            @Override
//            public void call(Pipeline pipeline) {
//                List<String> ids = Lists.newArrayListWithCapacity(10);
//                ts.forEach(t -> {
//                    ids.add(t.getId()+"");
//                    putAllToCache(pipeline, t);
//                });
//
//                RedisService.delete(idskey);
//                RedisService.putListCache(idskey, ids);
//            }
//        });
        RedisService.execute(new RedisBatchExecute() {
            @Override
            public void call(RedisOperations operations) {
                List<String> ids = Lists.newArrayListWithCapacity(10);
                ts.forEach(t -> {
                    ids.add(t.getId()+"");
                    putAllToCache(operations, t);
                });

                RedisService.delete(idskey);
                RedisService.putListCache(idskey, ids);
            }
        }, ts.hashCode()+"");
    }

    private void execMultiHashPipelined(List<T> ts, String idskey, List<String> idList){
//        RedisService.batchExecute(new RedisBatch() {
//            @Override
//            public void call(Pipeline pipeline) {
//                ts.forEach(t -> { putAllToCache(pipeline, t); });
//
//                RedisService.delete(idskey);
//                RedisService.putListCache(idskey, idList);
//            }
//        });
        RedisService.execute(new RedisBatchExecute() {
            @Override
            public void call(RedisOperations operations) {
                ts.forEach(t -> { putAllToCache(operations, t); });

                RedisService.delete(idskey);
                RedisService.putListCache(idskey, idList);
            }
        }, idList.hashCode()+"");
    }

//    private void putAllToCache(Pipeline pipeline, T t){
//        BeanHelper.registerConvertUtils();
//        Map<String, String> map = beanUtilsHashMapper.toHash(t);
//        pipeline.hmset(key(t.getId()), map);
//    }

    private void putAllToCache(RedisOperations operations, T t){
        BoundHashOperations<String, String, String> hashOperations = operations.boundHashOps(key(t.getId()));
        BeanHelper.registerConvertUtils();
        Map<String, String> map = beanUtilsHashMapper.toHash(t);
        hashOperations.putAll(map);
    }
}